/* 
 * libsaxychord
 *
 * Choi Jonghwan<saxboy@saxboy.pe.kr>
 * 2005/02/19
 *
 * - Written in KLDP CodeFest :-)
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "note.h"

NOTE note_New(void)
{
	NOTE n=NULL; 
	if ( (n=(NOTE)malloc(sizeof(_NOTE)))!=NULL)
		memset(n, 0, sizeof(_NOTE)); 

	return n; 
}

NOTE note_NewFromString(const char *notestr)
{
	NOTE n=NULL; 
	int _parse_note_string(const char *notestr); 

	if ( (n=note_New())==NULL) goto error; 
	if ( notestr==NULL) goto error; 

	n->abs_note = _parse_note_string(notestr); 

	return n; 

error:
	if ( n!=NULL) free(n); 
	n=NULL; 
	return n; 
}

NOTE note_Copy(NOTE n)
{
	NOTE newnote=NULL; 

	if (n==NULL) return NULL; 

	if ( (newnote=note_New())==NULL) goto error; 

	newnote->abs_note = n->abs_note; 

	return newnote; 

error:
	if ( newnote!=NULL) free(newnote); 
	newnote=NULL; 
	return newnote; 
}

int note_Delete(NOTE n)
{
	if (n==NULL) return -1; 
	free(n); 
	return 1; 
}

int note_SetLength(NOTE n, NOTE_LENGTH l)
{
	if (n==NULL) return -1; 
	n->length = l; 
	return 1; 
}
int note_GetLength(NOTE n)
{
	if (n==NULL) return -1; 
	return n->length; 
}

int note_RaiseHalf(NOTE n)
{
	//n->abs_note += 1; 
	return note_RaiseInterval(n, INTERVAL_MINOR, 2); 
}
int note_LowerHalf(NOTE n)
{
	//n->abs_note -= 1; 
	return note_LowerInterval(n, INTERVAL_MINOR, 2); 
}
int note_RaiseInterval(NOTE n, int interval_kind, int count )
{
	int interval =0; 
	if ( n==NULL) return -1; 
	if ( count < 0 ) return -1; 
	if ( interval_kind < 0 ) return -1; 

	interval = _calc_interval( interval_kind, count); 

	/* FIXME: check if given distance is valid */
	//if ( interval > n->abs_note ) return -1; 
	n->abs_note += interval; 
	n->abs_note = n->abs_note % 12; 
	
	return 1; 
}
int note_LowerInterval(NOTE n, int interval_kind, int count)
{
	int interval =0; 

	if ( n==NULL) return -1; 
	if ( count < 0 ) return -1; 
	if ( interval_kind < 0 ) return -1; 

	interval = _calc_interval( interval_kind, count); 

	/* check if given distance is valid */
	if ( interval > n->abs_note ) return -1; 
	n->abs_note -= interval; 
	n->abs_note = n->abs_note % 12; 

	return 1; 
}

int note_ExportToAbcString(NOTE n, char *buf, int buflen)
{
	int _convert_note_to_string_abc(int note, int note_flavor, char *notestr, int buflen); 

	if (n==NULL) return -1; 
	if (buf==NULL) return -1; 
	if (buflen <= 0 ) return -1; 

	if ( _convert_note_to_string_abc(n->abs_note, NOTE_FLAVOR_FLAT, buf, buflen) < 0 )
		return -1; 

	return 1; 

}

int note_ExportToString(NOTE n, char *buf, int buflen)
{
	int _convert_note_to_string(int note, int note_flavor, char *notestr, int buflen); 

	if (n==NULL) return -1; 
	if (buf==NULL) return -1; 
	if (buflen <= 0 ) return -1; 

	if ( _convert_note_to_string(n->abs_note, NOTE_FLAVOR_FLAT, buf, buflen) < 0 )
		return -1; 

	return 1; 
}

const char *note_GetStaticAbcString(NOTE n)
{
	static char buf[8]; 
	int _convert_note_to_string_abc( int note, int note_flavor, char *notestr, int buflen); 

	if ( n==NULL ) return "!NULL!"; 

	/* get note name for abc */
	if ( _convert_note_to_string_abc(n->abs_note, NOTE_FLAVOR_FLAT, buf, 8) < 0 )
	{
		printf(" note num=%d\n", n->abs_note); 
		sprintf(buf, "c"); 
	}

	return buf; 
}

int note_Print(NOTE n)
{
	char buf[4]={0,}; 

	if ( n==NULL) return -1; 
	note_ExportToString(n, buf, 4); 
	printf("NOTE: %s, abs=%d\n", buf, n->abs_note); 

	return 1; 
}

/* ------------------------------------------------------------
 * calculate interval: returns distance between the notes 
 * to calculate major second, 
 * we call _calc_interval(INTERVAL_MAJOR, 2)
 */ 
struct _interval_set 
{
	int interval_kind; 
	int count; 
	int interval; 
}; 
static struct _interval_set g_interval_set[] = 
{
	{INTERVAL_MINOR, 2, INTERVAL_MINOR_SECOND}, 
	{INTERVAL_MINOR, 3, INTERVAL_MINOR_THIRD}, 
	{INTERVAL_MAJOR, 3, INTERVAL_MAJOR_THIRD}, 
	{INTERVAL_PERFECT, 4, INTERVAL_PERFECT_FOURTH}, 
	{INTERVAL_AUGMENTED, 4, INTERVAL_AUGMENTED_FOURTH}, 
	{INTERVAL_DIMINISHED, 5, INTERVAL_DIMINISHED_FIFTH}, 
	{INTERVAL_PERFECT, 5, INTERVAL_PERFECT_FIFTH}, 
	{INTERVAL_AUGMENTED, 5, INTERVAL_AUGMENTED_FIFTH}, 
	{INTERVAL_MINOR, 6, INTERVAL_MINOR_SIXTH}, 
	{INTERVAL_MINOR, 7, INTERVAL_MINOR_SEVENTH}, 
	{INTERVAL_MAJOR, 7, INTERVAL_MAJOR_SEVENTH}, 
	{-1, -1, -1} /* the end of the definition */
}; 

int _calc_interval(int interval, int count)
{
	int i=0; 
	for (i=0; g_interval_set[i].count > 0; i++)
	{
		if ( g_interval_set[i].interval_kind == interval 
			&& g_interval_set[i].count == count )
				return g_interval_set[i].interval; 
	}

	return -1; 
}

/* ------------------------------------------------------------
 * convert note string to internal note representation
 */
struct __note_strings {
	int note; 
	char string[4]; 
	int note_flavor; /* preference: sharp or flat */
}; 
static struct __note_strings g_note_strings[] = {
	{ NOTE_C, "C", NOTE_FLAVOR_DONTCARE }, 
	{ NOTE_C_SHARP, "C#", NOTE_FLAVOR_SHARP }, 
	{ NOTE_D_FLAT, "Db", NOTE_FLAVOR_FLAT }, 
	{ NOTE_D, "D", NOTE_FLAVOR_DONTCARE }, 
	{ NOTE_D_SHARP, "D#", NOTE_FLAVOR_SHARP }, 
	{ NOTE_E_FLAT, "Eb", NOTE_FLAVOR_FLAT }, 
	{ NOTE_E, "E", NOTE_FLAVOR_DONTCARE }, 
	{ NOTE_E_SHARP, "E#", NOTE_FLAVOR_SHARP }, 
	{ NOTE_F_FLAT, "Fb", NOTE_FLAVOR_FLAT }, 
	{ NOTE_F, "F", NOTE_FLAVOR_DONTCARE }, 
	{ NOTE_F_SHARP, "F#", NOTE_FLAVOR_SHARP }, 
	{ NOTE_G_FLAT, "Gb", NOTE_FLAVOR_FLAT }, 
	{ NOTE_G, "G", NOTE_FLAVOR_DONTCARE }, 
	{ NOTE_G_SHARP, "G#", NOTE_FLAVOR_SHARP }, 
	{ NOTE_A_FLAT, "Ab", NOTE_FLAVOR_FLAT }, 
	{ NOTE_A, "A", NOTE_FLAVOR_DONTCARE }, 
	{ NOTE_A_SHARP, "A#", NOTE_FLAVOR_SHARP }, 
	{ NOTE_B_FLAT, "Bb", NOTE_FLAVOR_FLAT }, 
	{ NOTE_B, "B", NOTE_FLAVOR_DONTCARE }, 
	{ NOTE_B_SHARP, "B#", NOTE_FLAVOR_SHARP }, 
	{ NOTE_C_FLAT, "Cb", NOTE_FLAVOR_FLAT }, 
	{ -1, "", -1} 
}; 

static struct __note_strings g_note_strings_abc[] = {
	{ NOTE_C, "=C", NOTE_FLAVOR_DONTCARE }, 
	{ NOTE_C_SHARP, "^C", NOTE_FLAVOR_SHARP }, 
	{ NOTE_D_FLAT, "_D", NOTE_FLAVOR_FLAT }, 
	{ NOTE_D, "=D", NOTE_FLAVOR_DONTCARE }, 
	{ NOTE_D_SHARP, "^D", NOTE_FLAVOR_SHARP }, 
	{ NOTE_E_FLAT, "_E", NOTE_FLAVOR_FLAT }, 
	{ NOTE_E, "=E", NOTE_FLAVOR_DONTCARE }, 
	{ NOTE_E_SHARP, "^E", NOTE_FLAVOR_SHARP }, 
	{ NOTE_F_FLAT, "_F", NOTE_FLAVOR_FLAT }, 
	{ NOTE_F, "=F", NOTE_FLAVOR_DONTCARE }, 
	{ NOTE_F_SHARP, "^F", NOTE_FLAVOR_SHARP }, 
	{ NOTE_G_FLAT, "_G", NOTE_FLAVOR_FLAT }, 
	{ NOTE_G, "=G", NOTE_FLAVOR_DONTCARE }, 
	{ NOTE_G_SHARP, "^G", NOTE_FLAVOR_SHARP }, 
	{ NOTE_A_FLAT, "_A", NOTE_FLAVOR_FLAT }, 
	{ NOTE_A, "=A", NOTE_FLAVOR_DONTCARE }, 
	{ NOTE_A_SHARP, "^A", NOTE_FLAVOR_SHARP }, 
	{ NOTE_B_FLAT, "_B", NOTE_FLAVOR_FLAT }, 
	{ NOTE_B, "=B", NOTE_FLAVOR_DONTCARE }, 
	{ NOTE_B_SHARP, "^B", NOTE_FLAVOR_SHARP }, 
	{ NOTE_C_FLAT, "_C", NOTE_FLAVOR_FLAT }, 
	{ -1, "", -1} 
}; 


int _parse_note_string(const char *notestr)
{
	int i=0; 
	if ( notestr==NULL) return -1; 

	for (i=0; g_note_strings[i].note >= 0 ; i++)
	{
		//printf("g: %s, str=%s\n", g_note_strings[i].string, notestr); 
		if ( strcmp(g_note_strings[i].string, notestr)==0 )
		{
			return g_note_strings[i].note; 
		}
	}
	return -1; 
}

int _convert_note_to_string(
	int note, int note_flavor, char *notestr, int buflen)
{
	int i=0; 
	if ( notestr==NULL) return -1; 

	for (i=0; g_note_strings[i].note >= 0 ; i++)
	{
		if ( (g_note_strings[i].note) == (note%12) && 
			(g_note_strings[i].note_flavor == note_flavor ||
			 g_note_strings[i].note_flavor == NOTE_FLAVOR_DONTCARE) )
		{
			strncpy( notestr, g_note_strings[i].string, buflen);

			return 1; 
		}
	}
	return -1; 
}

int _convert_note_to_string_abc(
	int note, int note_flavor, char *notestr, int buflen)
{
	int i=0; 
	if ( notestr==NULL) return -1; 

	for (i=0; g_note_strings[i].note >= 0 ; i++)
	{
		if ( (g_note_strings[i].note) == (note%12) && 
			(g_note_strings[i].note_flavor == note_flavor ||
			 g_note_strings[i].note_flavor == NOTE_FLAVOR_DONTCARE) )
		{
			char *note_string = g_note_strings_abc[i].string; 

			if ( note_string[0]=='=')
				note_string++; 

			strncpy( notestr, note_string, buflen);
	
			/* FIXME: what about lower octave? */
			/* higher octave note */
			if ( note >= 12 ) 
			{
				notestr[1] = tolower(notestr[1]); 
			}


			return 1; 
		}
	}
	return -1; 
}
